﻿using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.FrontEndComponents;
using DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Sequencer;
using SharpDepend;
using SharpDepend.Datatypes;
using SoundEngine3.SequenceContent;
using System;
using System.Collections.Generic;
using System.Linq;
using ZquenceStudio3Host.Commands.BuiltInCommands;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Commands;
using ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Modules.Container;
using ZquenceStudio3Host.Graphics.Components;
using ZquenceStudio3Host.Graphics.Manager;
using ZquenceStudio3Host.Sequencer;
using ZquenceStudio3Host.TrackArea;
using ZquenceStudio3Host.TrackArea.Events;
using System.Collections.ObjectModel;
using SharpDepend.Classes.Threading;

namespace DefaultDspPlugins.UI.Common.Modulizer.Engine.GUIv2.Editors
{
    public class ModulizerTrackArea : AreaHandler<TrackAreaArgs, ITransformSequenceObject, BasicSequenceState>
    {
        class DummyDevice : IGraphicsDevice
        {
            public readonly static DummyDevice Instance = new DummyDevice();

            bool IGraphicsDevice.Enable { get { return false; } set { } }

            IGraphicsDisplay IGraphicsDevice.GraphicsDisplay { get { return null; } }

            bool IGraphicsDevice.ManualRendering { get { return false; } }

            bool IGraphicsDevice.Ready { get { return false; } }

            int IGraphicsDevice.RenderHeight { get { return 0; } }

            bool IGraphicsDevice.RenderTextures { get { return false; } set { } }

            int IGraphicsDevice.RenderWidth { get { return 0; } }

            ReadOnlyCollection<Texture> IGraphicsDevice.Textures { get { return null; } }

            event Action IGraphicsDevice.OnManualRender
            {
                add
                {
                }
                remove
                {
                }
            }

            event Action IGraphicsDevice.ResolutionChanged
            {
                add
                {
                }
                remove
                {
                }
            }

            void IGraphicsDevice.BeginRender()
            {
            }

            void IGraphicsDevice.DisposeDevice()
            {
            }

            void IGraphicsDevice.Draw(ref Vector4 rectangle, Color color)
            {
            }

            void IGraphicsDevice.Draw(ref Vector4 rectangle, Color color, Texture texture, ref Vector4 destinationTextureUV)
            {
            }

            void IGraphicsDevice.DrawCircle(Vector2 position, double radius, Color color)
            {
            }

            void IGraphicsDevice.DrawLine(Vector2 start, Vector2 end, Color color)
            {
            }

            void IGraphicsDevice.DrawTriangles(FastStructList<Vector2> vertices, FastStructList<ushort> indices, FastStructList<Color> colors)
            {
            }

            void IGraphicsDevice.EndRender()
            {
            }

            void IGraphicsDevice.Initialize(IGraphicsDisplay graphicsDisplay)
            {
            }

            void IGraphicsDevice.RegisterTexture(Texture texture)
            {
            }

            void IGraphicsDevice.ResetClip()
            {
            }

            void IGraphicsDevice.SetClip(Vector4 plane)
            {
            }

            void IGraphicsDevice.SetViewportSize(double width, double height)
            {
            }

            void IGraphicsDevice.UnregisterTexture(Texture texture)
            {
            }

            void IGraphicsDevice.UpdateTexture(Texture texture)
            {
            }
        }

        // Cached data.
        private class CachedModuleInfo
        {
            public IList<ModuleInput> Inputs;
            public IList<ModuleOutput> Outputs;
        }
        private Dictionary<BaseModule, CachedModuleInfo> mData = new Dictionary<BaseModule, CachedModuleInfo>();

        private ModulizerUIv2 mNodisizer;
        private NodisizerEditor mNodisizerEditor;
        private ShapesManager mLayerGraphics = new ShapesManager();
        private bool mGUIInited;
        private List<ModuleInput> mTmpInputs = new List<ModuleInput>();
        private List<ModuleOutput> mTmpOutputs = new List<ModuleOutput>();
        private List<ModuleInput> mTmpInputs2 = new List<ModuleInput>();
        private List<ModuleOutput> mTmpOutputs2 = new List<ModuleOutput>();
        private ModuleInput mSelectedInput;
        private ModuleOutput mSelectedInputLastConnectedOutput;
        private Vector2 mSelectedInputDrawPosition;
        private List<KeyValuePair<TrackModulePanel, ModuleUIPanel>> mCurrentModuleUIPanels = new List<KeyValuePair<TrackModulePanel, ModuleUIPanel>>();
        private new Label mLabel;
        private CallQueue mCallQueuer = new CallQueue();
        private bool mMouseDown;

        private Vector2 mMousePosition;
        private TransformModuleCommand mOngoingReferenceTransforming;
        private double mTrackXStart = 0;
        private double mTrackYStart = 0;
        private Button left;
        private Button move;
        private Button right;
        private Button upDown;

        internal double FrontEndPositionX { get { return mNodisizer.FrontEndPositionX; } }
        internal double FrontEndPositionY { get { return mNodisizer.FrontEndPositionY; } }

        public ModulizerTrackArea(ModulizerUIv2 nodisizer, double sequenceObjectHeight)
            : base(sequenceObjectHeight)
        {
            if (nodisizer == null)
            {
                throw new ArgumentNullException(nameof(nodisizer));
            }

            mNodisizer = nodisizer;

            MaxHeightIndexes = 100;

            CameraFillObjectMarginalHeight = 1;

            mNodisizerEditor.Initialize(mNodisizer);

            CurrentSnap = Snaps.NoSnap;

            RenderSimpleRows = false;
        }

        public override ITransformSequenceObject GetSequencerObject(int index)
        {
            BaseModule item = this.mNodisizer.CurrentModuleContainer.SubModules[index];

            ITransformSequenceObject modulePanel = this.mNodisizer.GetModulePanel(item);

            if (mNodisizer.GetModuleUIPanel(item) == null)
            {
                mNodisizer.AddModuleToFrontEnd(item, true);
            }

            return modulePanel;
        }

        public override int SequencerObjectCount()
        {
            return mNodisizer.CurrentModuleContainer.SubModules.Count;
        }

        public override void MouseHover(double x, double y)
        {

        }

        public override void MouseLeave()
        {

        }

        private FastStructList<Vector4> mOriginals = new FastStructList<Vector4>();

        protected override void OnAreaRender()
        {
            if (!mGUIInited && GraphicsHost.GraphicsDevice.RenderWidth != 0)
            {
                mGUIInited = true;
                GraphicsHost_DeviceLoaded();
            }

            // Render module panels.
            int count = SequencerObjectCount();
            mCurrentModuleUIPanels.Clear();
            mCurrentModuleUIPanels.Capacity = Math.Max(mCurrentModuleUIPanels.Capacity, count);

            lock (mCallQueuer)
            {
                for (int i = 0; i < count; i++)
                {
                    TrackModulePanel sequencerObject = (TrackModulePanel)GetSequencerObject(i);

                    if (sequencerObject != null)
                    {
                        sequencerObject.X = base.GetScreenPositionX(sequencerObject.Start);
                        sequencerObject.Y = base.GetScreenPositionY(sequencerObject.HeightPosition);
                        sequencerObject.Width = base.GetScreenPositionX(sequencerObject.Start + sequencerObject.DisplayLength) - sequencerObject.X;
                        sequencerObject.Height = base.GetScreenPositionY(sequencerObject.HeightPosition + sequencerObject.DisplayHeight) - sequencerObject.Y;
                        sequencerObject.Render(base.GraphicsHost.GraphicsDevice, this.ScreenParent);

                        // Try to get module ui panel.
                        ModuleUIPanel moduleUIPanel = this.mNodisizer.GetModuleUIPanel(sequencerObject.Module);
                        if (moduleUIPanel == null)
                        {
                            this.mLabel.X = sequencerObject.RealX;
                            this.mLabel.Width = sequencerObject.RealWidth;
                            this.mLabel.Height = Math.Min(50, sequencerObject.RealHeight);
                            this.mLabel.Y = sequencerObject.RealY + sequencerObject.RealHeight * 0.5 - this.mLabel.Height * 0.5;
                            this.mLabel.Text = sequencerObject.Module.Name;
                            this.mLabel.Render(base.GraphicsHost.GraphicsDevice, this.ScreenParent);
                        }
                        else
                        {
                            // Save temp.
                            Vector4 vector4 = this.ReformModule(sequencerObject, moduleUIPanel, false);
                            this.mOriginals.Add(vector4);
                            this.mCurrentModuleUIPanels.Add(new KeyValuePair<TrackModulePanel, ModuleUIPanel>(sequencerObject, moduleUIPanel));
                            sequencerObject.Module._IsRenderInTrackArea = true;
                            moduleUIPanel.Refresh();
                            moduleUIPanel.Render(base.GraphicsHost.GraphicsDevice, this.ScreenParent, false);
                            sequencerObject.Module._IsRenderInTrackArea = false;
                            moduleUIPanel.Refresh();
                        }
                    }
                }

                if (mMouseDown)
                {
                    mCallQueuer.CallAll();
                }

                int io = 0;
                for (int i = 0; i < count; i++)
                {
                    TrackModulePanel panel = (TrackModulePanel)GetSequencerObject(i);

                    if (panel != null)
                    {
                        // Try to get module ui panel.
                        ModuleUIPanel moduleUIPanel = mNodisizer.GetModuleUIPanel(panel.Module);

                        if (moduleUIPanel != null)
                        {
                            // Set temp.
                            DeformModule(moduleUIPanel, mOriginals[io++]);
                        }
                    }
                }
                mOriginals.Clear();
            }

            DrawLayer(GraphicsHost.GraphicsDevice);
        }

        private Vector4 ReformModule(TrackModulePanel panel, ModuleUIPanel moduleUIPanel, bool fakeIntersection)
        {
            moduleUIPanel.DimensionInProcent = false;
            Vector4 bound = new Vector4(moduleUIPanel.X, moduleUIPanel.Y, moduleUIPanel.X + moduleUIPanel.Width, moduleUIPanel.Y + moduleUIPanel.Height);

            double width = panel.RealWidth - 40;
            double height = panel.RealHeight - 40;
            double size = width > height ? height : width;

            moduleUIPanel.X = panel.X + panel.Width * 0.5 - size * 0.5;
            moduleUIPanel.Y = panel.Y + panel.Height * 0.5 - size * 0.5;
            moduleUIPanel.Width = size;
            moduleUIPanel.Height = size;
            if (fakeIntersection)
            {
                moduleUIPanel.Render(DummyDevice.Instance, ScreenParent, false);
            }
            return bound;
        }

        private static void DeformModule(ModuleUIPanel moduleUIPanel, Vector4 bound)
        {
            moduleUIPanel.DimensionInProcent = true;
            moduleUIPanel.X = bound.X;
            moduleUIPanel.Y = bound.Y;
            moduleUIPanel.Width = bound.Width;
            moduleUIPanel.Height = bound.Height;
        }

        internal void MouseDown(double x, double y, int id)
        {
            mMouseDown = true;

            // If mouse is hiting outside the area.
            if (!mNodisizer.IsInArea(x, y))
            {
                Freeze(true);
                return;
            }

            mSelectedInputLastConnectedOutput = null;
            mSelectedInput = PickInput(x, y, ref mSelectedInputDrawPosition);

            if (mSelectedInput != null)
            {
                mSelectedInputLastConnectedOutput = mSelectedInput.FromOutput;
                Freeze(true);
            }
            else
            {
                lock (mCallQueuer)
                {
                    mCallQueuer.QueueCall(delegate
                    {
                        // Iteract with module UIs.
                        foreach (var pair in mCurrentModuleUIPanels)
                        {
                            bool mousePressed = pair.Value.MouseDown(x, y, id);

                            if (mousePressed)
                            {
                                Freeze(true);
                                break;
                            }
                        }
                    });
                }

            }
        }

        internal void MouseMove(double x, double y, int id)
        {
            if (mSelectedInput != null)
            {
                mSelectedInputDrawPosition = new Vector2(x, y);
            }
            else
            {
                lock (mCallQueuer)
                {
                    mCallQueuer.QueueCall(delegate
                    {
                        // Iteract with module UIs.
                        foreach (var pair in mCurrentModuleUIPanels)
                        {
                            pair.Value.MouseMove(x, y, id);
                        }
                    });
                }
            }
        }

        internal void MouseUp(double x, double y, int id)
        {
            if (mSelectedInput != null)
            {
                if (mNodisizer.IsInArea(x, y))
                {
                    ModuleOutput pickedOutput = PickOutput(x, y, ref mSelectedInputDrawPosition);

                    if (pickedOutput != null)
                    {
                        // Do not reconnect and check if connection is possible.
                        if (mSelectedInput.FromOutput != pickedOutput && pickedOutput.PossibleToConnect(mSelectedInput))
                        {
                            if (!pickedOutput.CheckCircularConnection(mSelectedInput))
                            {
                                Connect(pickedOutput, mSelectedInput, mSelectedInputLastConnectedOutput);
                                mSelectedInputLastConnectedOutput = null;
                            }
                        }
                    }
                }
                else
                {
                    // Disconnect.
                    Disconnect(mSelectedInput);
                }

                mSelectedInput = null;
            }
            else
            {
                lock (mCallQueuer)
                {
                    mCallQueuer.QueueCall(delegate
                    {
                        // Iteract with module UIs.
                        for (int i = 0; i < mCurrentModuleUIPanels.Count; i++)
                        {
                            var pair = mCurrentModuleUIPanels[i];
                            pair.Value.MouseUp(x, y, id);
                        }
                    });
                }
            }

            Freeze(false);

            mMouseDown = false;
        }

        void Freeze(bool freeze)
        {
            TouchEnabled = !freeze;
        }

        protected override void OnSelectionUpdated()
        {
            // This method calls in base class constructor. Variable is not yet assigned.
            if (mNodisizer != null)
            {
                // If one item is selected. Make it ready for edit.
                if (Editor.SelectorHandler.SelectedObjects.Count == 1)
                {
                    var selectedModule = Editor.SelectorHandler.SelectedObjects[0];
                    BaseModule module = (selectedModule as TrackModulePanel).Module;

                    mNodisizer.Backend.Toolbox.SetModuleForEdit(module);
                }
                else
                {
                    // Just show the available module liste instead.
                    mNodisizer.Backend.Toolbox.SetModuleForEdit(null);
                }
            }
        }

        private void Disconnect(ModuleInput input)
        {
            DisconnectOutputCommand command = new DisconnectOutputCommand(mNodisizer, input);
            mNodisizer.UndoRedo.AddAndRunCommand(command);
        }

        public void Connect(ModuleOutput output, ModuleInput connectToInput, ModuleOutput wasConnectedToPeviousOutput)
        {
            if (connectToInput is InputModuleParameter.Input_ModuleInput)
            {
                InputModuleParameter.Input_ModuleInput inputModuleInput = (InputModuleParameter.Input_ModuleInput)connectToInput;

                if (inputModuleInput.Owner.IsAutomated)
                {
                    SharpDepend.Manager.DialogManager.Show(
                        "This input is automated and will be lost if you continue connect this input. No undo can be done, do you want to continue?", "Connection",
                        DialogButtons.YesNo, DialogIcon.Warning, Connect, new object[] { output, connectToInput, wasConnectedToPeviousOutput });
                    return;
                }
            }

            ConnectOutputCommand command = new ConnectOutputCommand(mNodisizer, output, connectToInput, wasConnectedToPeviousOutput);
            mNodisizer.UndoRedo.AddAndRunCommand(command);
        }

        void Connect(DialogButtonResult clickedButton, object parameter)
        {
            object[] data = (object[])parameter;
            ModuleOutput output = (ModuleOutput)data[0];
            ModuleInput connectToInput = (ModuleInput)data[1];
            ModuleOutput wasConnectedToPeviousOutput = (ModuleOutput)data[2];

            if (clickedButton == DialogButtonResult.Yes)
            {
                ConnectOutputCommand command = new ConnectOutputCommand(mNodisizer, output, connectToInput, wasConnectedToPeviousOutput);
                mNodisizer.UndoRedo.AddAndRunCommand(command);
            }
        }

        private ModuleInput PickInput(double x, double y, ref Vector2 position)
        {
            // Loop every module in current scene.
            foreach (var module in mNodisizer.CurrentModuleContainer.SubModules)
            {
                // Loop every visible inputs.
                foreach (var input in GetCachedInputs(module))
                {
                    bool accessible;
                    Vector4 circle = GetInputBound(input, out accessible);
                    if (accessible)
                    {
                        Vector2 center = new Vector2(circle.CenterX, circle.CenterY);
                        Vector2 tap = new Vector2(x, y);
                        double distance = Vector2.DistanceBetween(center, tap);


                        if (distance <= circle.Height)
                        {
                            position = tap;
                            return input;
                        }
                    }
                }
            }

            return null;
        }

        private ModuleOutput PickOutput(double x, double y, ref Vector2 position)
        {
            // Loop every module in current scene.
            foreach (var module in mNodisizer.CurrentModuleContainer.SubModules)
            {
                // Loop every visible outputs.
                foreach (var output in GetCachedOutputs(module))
                {
                    bool accessible;
                    Vector4 circle = GetOutputBound(output, out accessible);
                    if (accessible)
                    {
                        Vector2 center = new Vector2(circle.CenterX, circle.CenterY);
                        Vector2 tap = new Vector2(x, y);
                        double distance = Vector2.DistanceBetween(center, tap);


                        if (distance <= circle.Height)
                        {
                            position = tap;
                            return output;
                        }
                    }
                }
            }

            return null;
        }

        internal void RefreshCache()
        {
            foreach (var module in mNodisizer.mModuleManager.Modules)
            {
                if (!mData.ContainsKey(module))
                {
                    mData.Add(module, new CachedModuleInfo());
                }

                // Inputs.
                mData[module].Inputs = module.Inputs;
                if (module is GroupModule)
                {
                    mData[module].Inputs = (module as GroupModule).GetVisibleInputs();
                }

                // Outputs.
                mData[module].Outputs = module.Outputs;
                if (module is GroupModule)
                {
                    mData[module].Outputs = (module as GroupModule).GetVisibleOutputs();
                }
            }
        }

        IList<ModuleInput> GetCachedInputs(BaseModule module)
        {
            if (!mData.ContainsKey(module))
            {
                RefreshCache();
            }
            return mData[module].Inputs;
        }

        bool ModuleExistOutsideThisGroup(GroupModule currentGroup, BaseModule module)
        {
            if (currentGroup.ParentModule == null)
            {
                return false;
            }

            if ((currentGroup.ParentModule as GroupModule).SubModules.Contains(module))
            {
                return true;
            }

            return ModuleExistOutsideThisGroup((currentGroup.ParentModule as GroupModule), module);
        }

        IList<ModuleOutput> GetCachedOutputs(BaseModule module)
        {
            if (!mData.ContainsKey(module))
            {
                RefreshCache();
            }
            return mData[module].Outputs;
        }

        void DrawLayer(IGraphicsDevice device)
        {
            mLayerGraphics.BeginDraw();

            // Text color.
            double scale = Math.Max(PinchArea.Scale.X, PinchArea.Scale.Y) * 0.35;
            scale = Math.Min(1.0, Math.Max(0, scale));

            if (scale < 1.0)
            {
                scale *= scale * scale;
            }

            mLabel.TextColor = new Color(1.0, 1.0, 1.0, scale);


            int count = SequencerObjectCount();
            for (int i = 0; i < count; i++)
            {
                TrackModulePanel panel = (TrackModulePanel)GetSequencerObject(i);
                if (panel != null)
                {
                    Vector4 bound = new Vector4(panel.RealX, panel.RealY, panel.RealX + panel.RealWidth, panel.RealY + panel.RealHeight);
                    if (bound.Width < 5)
                    {
                        bound.Width = 5;
                    }
                    if (bound.Height < 5)
                    {
                        bound.Height = 5;
                    }

                    double visualWidth = Math.Max(bound.Width / 3, 40);
                    double visualHeight = 40;
                    
                    // Cached.
                    IList<ModuleInput> inputs = GetCachedInputs(panel.Module);
                    IList<ModuleOutput> outputs = GetCachedOutputs(panel.Module);


                    int position = 0;
                    double heightPosition;
                    
                    // Draw Outputs and waves.
                    if (outputs.Count > 0)
                    {
                        heightPosition = bound.Height / outputs.Count;
                        visualHeight = Math.Min(40, bound.Height / outputs.Count);
                        foreach (var output in outputs)
                        {
                            if (output.Module.ID == -1)
                            {
                                // Module do not exist anymore.
                                RefreshCache();
                                goto skip;
                            }

                            double hPos = position * heightPosition;
                    
                            double posX1 = bound.X2;
                            double posY1 = bound.Y1 + hPos + 1;
                            double posX2 = bound.X2;
                            double posY2 = bound.Y1 + hPos + visualHeight - 1;
                            Vector4 drawBound = new Vector4(posX1, posY1, posX2, posY2);

                            double outputRadian = visualHeight * 0.5;

                            // Color
                            double weight = Math.Min(3, 1 + (output.IsConnected ? output.VisualForce * 4 : 0));
                            Color color = GetColor(output);
                            Color audioColor = color * weight;

                            if (output.ActualType == IOType.Midi)
                            {
                                audioColor = color * (output.VisualForce * 4);
                                audioColor.A = 255;
                                mLayerGraphics.DrawFilledCircle(drawBound.CenterX, drawBound.CenterY, outputRadian, audioColor, color);
                            }
                            else
                            {
                                //Color color = GetColor(output);
                                mLayerGraphics.DrawFilledCircle(drawBound.CenterX, drawBound.CenterY, outputRadian, Color.Black, audioColor);
                                DrawWaves(mLayerGraphics, (int)(posX1 - outputRadian), (int)(posX2 + outputRadian), (int)posY1, (int)posY2, output.AudioBuffer.Buffer, audioColor);
                            }

                            // Draw output labels.
                            mLabel.X = drawBound.X2 + outputRadian;
                            mLabel.Y = drawBound.Y1;
                            mLabel.Width = ScreenParent.Width;
                            mLabel.Height = drawBound.Height;
                            mLabel.Text = output.ShortName;
                            mLabel.BackgroundColor = Color.Transparent;
                            mLabel.Render(GraphicsHost.GraphicsDevice, ScreenParent);

                            ++position;

                            foreach (var connectedInput in output.ConnectedInputs)
                            {
                                bool existOutside = ModuleExistOutsideThisGroup(mNodisizer.CurrentModuleContainer, connectedInput.Module);

                                if (existOutside)
                                {
                                    Vector2 dummyPos1 = new Vector2(mLabel.X, drawBound.CenterY);
                                    Vector2 dummyPos2 = new Vector2(GraphicsHost.GraphicsDevice.RenderWidth, drawBound.CenterY);
                                    mLayerGraphics.DrawLine(dummyPos1.X, dummyPos1.Y, dummyPos2.X, dummyPos2.Y, GetColor(output));
                                }
                            }
                        }
                    }
                    
                    // Draw inputs.
                    if (inputs.Count > 0)
                    {
                        position = 0;
                        heightPosition = bound.Height / inputs.Count;
                        visualHeight = Math.Min(40, bound.Height / inputs.Count);
                        foreach (var input in inputs)
                        {
                            if (input.Module.ID == -1)
                            {
                                // Module do not exist anymore.
                                RefreshCache();
                                goto skip;
                            }

                            double hPos = position * heightPosition;
            
                            double posX1 = bound.X1;
                            double posY1 = bound.Y1 + hPos + 1;
                            double posX2 = bound.X1;
                            double posY2 = bound.Y1 + hPos + visualHeight - 1;
                            double inputRadian = visualHeight * 0.5;
                            Vector4 drawBound = new Vector4(posX1, posY1, posX2, posY2);

                            bool connected = input.IsConnected;

                            // Color
                            double weight = Math.Min(3, 1 + (connected ? input.FromOutput.VisualForce * 4 : 0));
                            Color audioColor = GetColor(input) * weight;

                            mLayerGraphics.DrawFilledCircle(drawBound.CenterX, drawBound.CenterY, inputRadian, Color.Black, audioColor);
                            ++position;
            
                            // Draw connection.
                            bool accessible;
                            Vector4 boundInput = GetInputBound(input, out accessible);
                            if (!accessible)
                            {
                                //Vector2 dummyPos1 = new Vector2(p1.Parent.RealX + p1.Parent.RealWidth, p1.RealY + p1.RealHeight * 0.5);
                                //Vector2 dummyPos2 = new Vector2(GraphicsHost.GraphicsDevice.RenderWidth, p1.RealY + p1.RealHeight * 0.5);
                                //canvas.DrawLine(dummyPos1, dummyPos2, color);
                            }
            
                            if (connected)
                            {
                                Vector4 boundOutput = GetOutputBound(input.FromOutput, out accessible);
                                if (!accessible)
                                {
                                    // Draw a endless connection to the left.
                                    Vector2 dummyPos1 = new Vector2(0, boundInput.CenterY);
                                    Vector2 dummyPos2 = new Vector2(boundInput.CenterX, boundInput.CenterY);
                                    mLayerGraphics.DrawLine(dummyPos1.X, dummyPos1.Y, dummyPos2.X, dummyPos2.Y, audioColor);
                                }
                                else
                                {
                                    // Draw durven connection.
                                    double outputRadian = boundOutput.Height * 0.5;
                                    Vector2 pos1 = new Vector2(boundInput.X1 - inputRadian, boundInput.CenterY);
                                    Vector2 pos2 = new Vector2(boundOutput.X2 + outputRadian, boundOutput.CenterY);

                                    if (input == mSelectedInput)
                                    {
                                        pos2 = mSelectedInputDrawPosition;
                                    }

                                    DrawCurvedLine(mLayerGraphics, pos1, pos2, audioColor);
                                }
                            }
                            else if (mSelectedInputLastConnectedOutput == null && mSelectedInput == input)
                            {
                                // Draw non connected.
                                Vector2 pos1 = new Vector2(boundInput.X1 - inputRadian, boundInput.CenterY);
                                DrawCurvedLine(mLayerGraphics, pos1, mSelectedInputDrawPosition, audioColor);
                            }

                            // Draw output labels.
                            mLabel.X = 0;
                            mLabel.Y = drawBound.Y1;
                            mLabel.Width = ScreenParent.Width;
                            mLabel.Height = drawBound.Height;
                            mLabel.Text = input.ShortName;
                            mLabel.Render(null, ScreenParent);
                            mLabel.X = (drawBound.X1 - inputRadian) - mLabel.LastCalculatedTextWidth;
                            mLabel.Render(GraphicsHost.GraphicsDevice, ScreenParent);
                        }
                    }
                }
            }

            skip:;
            
            mLayerGraphics.EndDraw();
        }

        private void DrawWaves(ShapesManager canvas, int left, int right, int top, int bottom, double[] audio, Color color)
        {
            int width = right - left;
            int height = bottom - top;
            double middle = top + (bottom - top) * 0.5;
            int waveResolution = (int)width;
            double iRes = audio.Length / waveResolution;

            Vector2 lastPoint = Vector2.Zero;
            for (int i = 0; i < waveResolution; i++)
            {
                Vector2 newPoint = Vector2.Zero;

                double value = -audio[(int)(i * iRes)];

                value = Math.Min(1.0, Math.Max(-1, value)) * 0.5;

                newPoint.X = left + (width / waveResolution) * i;
                newPoint.Y = middle + value * height;

                if (i != 0)
                {
                    canvas.DrawLine(lastPoint.X, lastPoint.Y, newPoint.X, newPoint.Y, color, 0.7);
                }

                lastPoint = newPoint;
            }
        }

        Vector4 GetInputBound(ModuleInput input, out bool accessible)
        {
            TrackModulePanel panel = mNodisizer.GetModuleUIPanel(input.Module, mNodisizer.CurrentModuleContainer);
            int position;

            IList<ModuleInput> inputs = null;
            if (panel.Module is GroupModule)
            {
                // Cached.
                inputs = GetCachedInputs(panel.Module);
            }
            else
            {
                inputs = panel.Module.Inputs;
            }
            position = inputs.IndexOf(input);

            if (panel == null)
            {
                accessible = false;
                return Vector4.Zero;
            }
            accessible = true;
            
            Vector4 bound = new Vector4(panel.RealX, panel.RealY, panel.RealX + panel.RealWidth, panel.RealY + panel.RealHeight);
            int heightPosition = (int)bound.Height / inputs.Count;
            
            double visualWidth = Math.Max(bound.Width / 3, 40);
            double visualHeight = Math.Min(40, bound.Height / inputs.Count);
            
            double hPos = position * heightPosition;
            double posX1 = bound.X1;
            double posY1 = bound.Y1 + hPos + 1;
            double posX2 = bound.X1;
            double posY2 = bound.Y1 + hPos + visualHeight - 1;
            return new Vector4(posX1, posY1, posX2, posY2);
        }

        Vector4 GetOutputBound(ModuleOutput output, out bool accessible)
        {
            TrackModulePanel panel = mNodisizer.GetModuleUIPanel(output.Module, mNodisizer.CurrentModuleContainer);
            int position;

            if (panel == null)
            {
                accessible = false;
                return Vector4.Zero;
            }

            IList<ModuleOutput> outputs = null;

            // TODO: Optimera.
            if (panel.Module is GroupModule)
            {
                // Cached.
                outputs = GetCachedOutputs(panel.Module);
            }
            else
            {
                outputs = panel.Module.Outputs;
            }

            position = outputs.IndexOf(output);

            if (panel == null)
            {
                accessible = false;
                return Vector4.Zero;
            }
            accessible = true;

            Vector4 bound = new Vector4(panel.RealX, panel.RealY, panel.RealX + panel.RealWidth, panel.RealY + panel.RealHeight);
            int heightPosition = (int)bound.Height / outputs.Count;

            double visualWidth = Math.Max(bound.Width / 3, 40);
            double visualHeight = Math.Min(40, bound.Height / outputs.Count);

            double hPos = position * heightPosition;
            double posX1 = bound.X2;
            double posY1 = bound.Y1 + hPos + 1;
            double posX2 = bound.X2;
            double posY2 = bound.Y1 + hPos + visualHeight - 1;
            return new Vector4(posX1, posY1, posX2, posY2);
        }

        internal static Color GetColor(ModuleIO moduleIO)
        {
            switch (moduleIO.ActualType)
            {
                case IOType.Audio:
                    return ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI.Controls.ModulePanel.COLOR_AUDIO;
                case IOType.Midi:
                    return ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI.Controls.ModulePanel.COLOR_MIDI;
                case IOType.Waveform:
                    return ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI.Controls.ModulePanel.COLOR_WAVEFORM;
                case IOType.AudioOrWaveform:
                    return ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.GUI.Controls.ModulePanel.COLOR_AUDIO_OR_WAVEFORM;
            }

            return Color.Pink;
        }

        static void DrawCurvedLine(ShapesManager canvas, Vector2 pos1, Vector2 pos2, Color color)
        {
            double distance = SharpDepend.Utils.Math.Distance(pos1, pos2);
            int quality = System.Math.Min(80, System.Math.Max(3, (int)(distance * 0.05)));

            Vector2 center = pos1 + (pos2 - pos1) * 0.5;
            Vector2 middleUp = new Vector2(center.X, pos2.Y);
            Vector2 middleDown = new Vector2(center.X, pos1.Y);

            CurvedLine(canvas, pos1, middleDown, middleUp, pos2, color, quality);
        }

        static void CurvedLine(ShapesManager canvas, Vector2 a, Vector2 b, Vector2 c, Color color, int quality)
        {
            Vector2 lastCurvedDot = a;

            for (int i = 0; i < quality + 1; i++)
            {
                double percent = 1.0 / quality * i;

                // Step 1.
                Vector2 pointA = Leap(a, b, percent);
                Vector2 pointB = Leap(b, c, percent);

                // Step 2.
                Vector2 curvedDot = Leap(pointA, pointB, percent);

                canvas.DrawLine(lastCurvedDot.X, lastCurvedDot.Y, curvedDot.X, curvedDot.Y, color);

                lastCurvedDot = curvedDot;
            }
        }

        static void CurvedLine(ShapesManager canvas, Vector2 a, Vector2 b, Vector2 c, Vector2 d, Color color, int quality)
        {
            Vector2 lastCurvedDot = a;

            for (int i = 0; i < quality + 1; i++)
            {
                double percent = 1.0 / quality * i;

                // Step 1.
                Vector2 pointA = Leap(a, b, percent);
                Vector2 pointB = Leap(b, c, percent);
                Vector2 pointC = Leap(c, d, percent);

                // Step 2.
                Vector2 curvedA = Leap(pointA, pointB, percent);
                Vector2 curvedB = Leap(pointB, pointC, percent);

                // Step 3.
                Vector2 curvedDot = Leap(curvedA, curvedB, percent);

                canvas.DrawLine(lastCurvedDot.X, lastCurvedDot.Y, curvedDot.X, curvedDot.Y, color);

                lastCurvedDot = curvedDot;
            }
        }

        static Vector2 Leap(Vector2 a, Vector2 b, double percent)
        {
            return a + (b - a) * percent;
        }

        protected override void OnSelectorInitialized()
        {
            mLabel = new Label(GuiManager);
            mLabel.AutoSize = true;

            const double BUTTON_SIZE = 80;
            Color buttonColor = Color.DarkGray;
            buttonColor.A = 180;

            left = new Button(GuiManager);
            left.SetButtonColor(buttonColor);
            left.setTextureIcon("Images/Icons/arrow_left.png");
            left.Events.OnMousePressing += delegate (object sender, double x, double y, int id)
            {
                // Get mouse down track position.
                mTrackXStart = GetTrackPositionX(x, false);
                mTrackYStart = GetTrackPositionY(y);

                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                BeginProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseStart(x, y);
            };
            left.Events.OnMouseMoving += delegate (object sender, double x, double y, int id)
            {
                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                ProcessScreenMovement(Editor.SequencerArguments);

                mOngoingReferenceTransforming.DeltaMoveLeft(
                    Snap(GetTrackPositionX(x, false) - mTrackXStart));
                Editor.SelectorHandler.Update();
            };
            left.Events.OnMouseUnpressing += delegate
            {
                EndProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseRelease();
            };
            left.Width = BUTTON_SIZE;
            left.Height = BUTTON_SIZE;
            AddSelectorButton(left, 0);

            move = new Button(GuiManager);
            move.SetButtonColor(buttonColor);
            move.setTextureIcon("Images/Icons/move.png");
            move.Events.OnMousePressing += delegate (object sender, double x, double y, int id)
            {
                // Get mouse down track position.
                mTrackXStart = GetTrackPositionX(x, false);
                mTrackYStart = GetTrackPositionY(y);

                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                BeginProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseStart(x, y);
            };
            move.Events.OnMouseMoving += delegate (object sender, double x, double y, int id)
            {
                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                ProcessScreenMovement(Editor.SequencerArguments);

                // Move a reference
                mOngoingReferenceTransforming.DeltaMove(
                    Snap(GetTrackPositionX(x, false) - mTrackXStart),
                    GetTrackPositionY(y) - mTrackYStart);
                Editor.SelectorHandler.Update();
            };
            move.Events.OnMouseUnpressing += delegate
            {
                EndProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseRelease();
            };
            move.Width = BUTTON_SIZE;
            move.Height = BUTTON_SIZE;
            AddSelectorButton(move, 0);

            right = new Button(GuiManager);
            right.SetButtonColor(buttonColor);
            right.setTextureIcon("Images/Icons/arrow_right.png");
            right.Events.OnMousePressing += delegate (object sender, double x, double y, int id)
            {
                // Get mouse down track position.
                mTrackXStart = GetTrackPositionX(x, false);
                mTrackYStart = GetTrackPositionY(y);

                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                BeginProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseStart(x, y);
            };
            right.Events.OnMouseMoving += delegate (object sender, double x, double y, int id)
            {
                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                ProcessScreenMovement(Editor.SequencerArguments);

                mOngoingReferenceTransforming.DeltaMoveRight(
                    Snap(GetTrackPositionX(x, false) - mTrackXStart));
                Editor.SelectorHandler.Update();
                UpdateSelectorButtons();
            };
            right.Events.OnMouseUnpressing += delegate
            {
                EndProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseRelease();
            };
            right.Width = BUTTON_SIZE;
            right.Height = BUTTON_SIZE;
            AddSelectorButton(right, 0);

            Button unselect = new Button(GuiManager);
            unselect.SetButtonColor(buttonColor);
            unselect.setTextureIcon("Images/Icons/icon_unmark.png");
            unselect.OnClicked += delegate
            {
                Editor.SelectorHandler.ClearSelection();
                mNodisizer.Backend.Toolbox.SetModuleForEdit(null);
            };
            unselect.Width = BUTTON_SIZE;
            unselect.Height = BUTTON_SIZE;
            AddSelectorButton(unselect, 1);


            upDown = new Button(GuiManager);
            upDown.SetButtonColor(buttonColor);
            upDown.setTextureIcon("Images/Icons/arrow_down.png");
            upDown.Events.OnMousePressing += delegate (object sender, double x, double y, int id)
            {
                // Get mouse down track position.
                mTrackXStart = GetTrackPositionX(x, false);
                mTrackYStart = GetTrackPositionY(y);

                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                BeginProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseStart(x, y);
            };
            upDown.Events.OnMouseMoving += delegate (object sender, double x, double y, int id)
            {
                Editor.SequencerArguments.PositionX = x;
                Editor.SequencerArguments.PositionY = y;
                ProcessScreenMovement(Editor.SequencerArguments);

                mOngoingReferenceTransforming.DeltaMoveUpDown(
                    GetTrackPositionY(y) - mTrackYStart);
                Editor.SelectorHandler.Update();
                UpdateSelectorButtons();
            };
            upDown.Events.OnMouseUnpressing += delegate
            {
                EndProcessScreenMovement(Editor.SequencerArguments);

                SelectorButtonMouseRelease();
            };
            upDown.Width = BUTTON_SIZE;
            upDown.Height = BUTTON_SIZE;
            AddSelectorButton(upDown, 1);

            Button duplicate = new Button(GuiManager);
            duplicate.SetButtonColor(buttonColor);
            duplicate.setTextureIcon("Images/Icons/icon_x2.png");
            duplicate.OnClicked += delegate
            {
                var selectedReferences = Editor.SelectorHandler.SelectedObjects;
                DuplicateModules(selectedReferences);
                mNodisizer.Backend.Toolbox.SetModuleForEdit(null);
            };
            duplicate.Width = BUTTON_SIZE;
            duplicate.Height = BUTTON_SIZE;
            AddSelectorButton(duplicate, 1);

            Button goInside = new Button(GuiManager);
            goInside.SetButtonColor(buttonColor);
            goInside.setTextureIcon("Images/Icons/zoomin.png");
            goInside.OnClicked += delegate
            {
                var selectedReferences = Editor.SelectorHandler.SelectedObjects;

                TrackModulePanel module = (TrackModulePanel)selectedReferences[0];

                mNodisizer.GoInto(module.Module);

                mNodisizer.Backend.Toolbox.SetModuleForEdit(null);
            };
            goInside.Width = BUTTON_SIZE;
            goInside.Height = BUTTON_SIZE;
            AddSelectorButton(goInside, 1, delegate (Panel sender)
            {
                // Only one can be selected.
                if (Editor.SelectorHandler.SelectedObjects.Count == 1)
                {
                    var selectedReferences = Editor.SelectorHandler.SelectedObjects;

                    TrackModulePanel module = (TrackModulePanel)selectedReferences[0];

                    // We can only go inside a container module.
                    if (module.Module is IModuleContainer)
                    {
                        return true;
                    }
                }

                return false;
            });

            Button makeGroup = new Button(GuiManager);
            makeGroup.SetButtonColor(buttonColor);
            //makeGroup.setTextureIcon("Images/Icons/icon_x2.png");
            makeGroup.setText("Group");
            makeGroup.OnClicked += delegate
            {
                var selectedReferences = Editor.SelectorHandler.SelectedObjects;

                mNodisizer.MakeGroup(selectedReferences);
            };
            makeGroup.Width = BUTTON_SIZE;
            makeGroup.Height = BUTTON_SIZE;
            AddSelectorButton(makeGroup, 1, delegate (Panel sender)
            {
                return Editor.SelectorHandler.SelectedObjects.Count > 1;
            });

            Button remove = new Button(GuiManager);
            remove.SetButtonColor(buttonColor);
            remove.setTextureIcon("Images/Icons/delete.png");
            remove.OnClicked += delegate
            {
                var selectedModules = Editor.SelectorHandler.SelectedObjects;
                RemoveModules(selectedModules);
                Editor.SelectorHandler.Update();
                mNodisizer.Backend.Toolbox.SetModuleForEdit(null);
            };
            remove.Width = BUTTON_SIZE;
            remove.Height = BUTTON_SIZE;
            AddSelectorButton(remove, 1);
        }

        private void GraphicsHost_DeviceLoaded()
        {
            //int width = GraphicsHost.GraphicsDevice.RenderWidth;
            //int height = GraphicsHost.GraphicsDevice.RenderHeight;
            //mLayerGraphics = new TexturePanel(GuiManager, width, height, true);
            //mLayerGraphics.BackgroundColor = Color.White;
            //mLayerGraphics.Width = width;
            //mLayerGraphics.Height = height;

            // Must be added in GuiManager, to make texture editing thread-safe.
            //GuiManager.MainPanel.AddPart(mLayerGraphics);
            
            mLayerGraphics.SetGraphicsDevice(GraphicsHost.GraphicsDevice);
        }

        public IEnumerable<BaseModule> DuplicateModules(IEnumerable<ITransformSequenceObject> modulePanels)
        {
            if (modulePanels == null)
            {
                throw new ArgumentNullException(nameof(modulePanels));
            }

            bool wasProtectedModule = false;

            List<BaseModule> modules = new List<BaseModule>(modulePanels.Count());
            foreach (TrackModulePanel modulePanel in modulePanels)
            {
                if (!mNodisizer.mModuleManager.IsProtectedModule(modulePanel.Module))
                {
                    modules.Add(modulePanel.Module);
                }
                else
                {
                    wasProtectedModule = true;
                    break;
                }
            }

            if (wasProtectedModule)
            {
                SharpDepend.Manager.DialogManager.Show("Can not duplicate protected module.", "Duplicate module", SharpDepend.DialogIcon.Warning);
            }
            else
            {
                DuplicateModuleCommand command = new DuplicateModuleCommand(this, mNodisizer, modules);
                mNodisizer.UndoRedo.AddAndRunCommand(command);
                return command.NewModules;
            }

            modules.Clear();
            return modules;
        }

        private void RemoveModules(IEnumerable<ITransformSequenceObject> modulePanels)
        {
            if (modulePanels == null)
            {
                throw new ArgumentNullException(nameof(modulePanels));
            }

            bool wasProtectedModule = false;

            EmptyCommand command = new EmptyCommand("Removed modules.");
            foreach (TrackModulePanel modulePanel in modulePanels)
            {
                if (!mNodisizer.mModuleManager.IsProtectedModule(modulePanel.Module))
                {
                    RemoveModuleCommand com = new RemoveModuleCommand(mNodisizer, modulePanel.Module);
                    command.Merge(com);
                }
                else
                {
                    wasProtectedModule = true;
                }
            }

            mNodisizer.UndoRedo.AddAndRunCommand(command);

            if (wasProtectedModule)
            {
                SharpDepend.Manager.DialogManager.Show("Can not remove protected module.", "Remove module", SharpDepend.DialogIcon.Warning);
            }
        }

        private void SelectorButtonMouseStart(double x, double y)
        {
            mMousePosition.X = x;
            mMousePosition.Y = y;
            mOngoingReferenceTransforming = new TransformModuleCommand(HostContext.Application, mNodisizer, this, Editor.SelectorHandler.SelectedObjects);
        }

        private void SelectorButtonMouseRelease()
        {
            if (mOngoingReferenceTransforming != null)
            {
                if (mOngoingReferenceTransforming.FinishTransform())
                {
                    mNodisizer.UndoRedo.AddAndRunCommand(mOngoingReferenceTransforming);
                }
                mOngoingReferenceTransforming = null;
            }
        }

        protected override void OnUpdatedAfterMouseMove()
        {
            Editor.SelectorHandler.Update();
        }

        protected override void OnCameraAnimation()
        {
            // Calls every frame when camera is animating.
            Editor.SelectorHandler.Update();
        }

        protected override void RenderTimelines()
        {
            // Override this to remove rendering of the timelines.
        }

        protected override BaseSequencerEditor<TrackAreaArgs, ITransformSequenceObject, BasicSequenceState> OnCreateEditorInstance()
        {
            mNodisizerEditor = new NodisizerEditor(this, SequenceObjectHeight);
            return mNodisizerEditor;
        }

        internal class NodisizerEditor : BaseSequencerEditor<TrackAreaArgs, ITransformSequenceObject, BasicSequenceState>
        {
            private ModulizerUIv2 mNodisizer;
            private ModulizerTrackArea mBaseArea;

            public NodisizerEditor(ModulizerTrackArea baseArea, double sequenceObjectHeight)
                : base(baseArea, sequenceObjectHeight)
            {
                if (baseArea == null)
                {
                    throw new ArgumentNullException(nameof(baseArea));
                }
                mBaseArea = baseArea;
            }

            internal void Initialize(ModulizerUIv2 nodisizer)
            {
                if (nodisizer == null)
                {
                    throw new ArgumentNullException(nameof(nodisizer));
                }
                mNodisizer = nodisizer;
            }

            protected override IEnumerable<ITransformSequenceObject> OnCloned(IEnumerable<ITransformSequenceObject> objects)
            {
                IEnumerable<BaseModule> modules = mBaseArea.DuplicateModules(objects);

                List<TrackModulePanel> panels = new List<TrackModulePanel>();
                int count = mBaseArea.SequencerObjectCount();
                for (int i = 0; i < count; i++)
                {
                    TrackModulePanel panel = (TrackModulePanel)mBaseArea.GetSequencerObject(i);

                    if (modules.Contains(panel.Module))
                    {
                        panels.Add(panel);
                    }
                }

                return panels;
            }

            protected override ITransformSequenceObject OnCreatedObject(double x, double y)
            {
                TrackModulePanel module = mNodisizer.CreateModule();

                if (module != null)
                {
                    Vector4 bound = new Vector4(x, y, x + 100, y + 1);

                    AddModuleCommand command = new AddModuleCommand(mNodisizer, bound, module.Module);
                    mNodisizer.UndoRedo.AddAndRunCommand(command);

                    return mNodisizer.GetModulePanel(command.AddedModule);
                }
                else
                {
                    return null;
                }
            }

            protected override void OnPickedSequeneObject(ITransformSequenceObject sequenceObject)
            {
                mNodisizer.UpdateModuleForEdit((sequenceObject as TrackModulePanel).Module);
            }

            protected override BaseTransformCommand<ITransformSequenceObject, BasicSequenceState> OnCreatedTransforCommandObject(IEnumerable<ITransformSequenceObject> objects)
            {
                return new TransformModuleCommand(Application, mNodisizer, mBaseArea, objects);
            }

            protected override void OnInitialized()
            {

            }

            protected override void OnRemovedObject(ITransformSequenceObject obj)
            {
                throw new NotImplementedException();
            }
        }
    }
}
